package http

import (
	"encoding/json"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	"math"
	"net/http"
	_ "net/http/pprof"
	"strings"
	"thomas-mesh/discovery"
	"thomas-mesh/pkg"
	"time"
)

var (
	transport = http.Transport{
		DisableKeepAlives: true,
	}

	InternalClient = &http.Client{
		Transport: &transport,
		Timeout:   10 * time.Second,
	}
)

func Server(addr string) {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		defer func() {
			if e := recover(); e != nil {
				log.Errorf("recover:%v", e)
			}
		}()
		// String()方法带查询参数
		path := request.URL.String()
		if handleInternalPath(request, path, writer) {
			return
		}
		serviceName := resolveServiceName(request)
		if serviceName == "" {
			writer.WriteHeader(404)
			return
		}
		subpath := strings.Replace(path, "/"+serviceName, "", 1)
		serviceAddr := discovery.DiscoveryService(serviceName)
		if serviceAddr == "" {
			log.Errorf("serviceName:%s  address not found", serviceName)
			return
		}
		serviceAddress := "http://" + serviceAddr
		targetUrl := serviceAddress + subpath
		req, error := http.NewRequest(request.Method, targetUrl, request.Body)
		if error != nil {
			log.Error(error)
			return
		}
		startTime := time.Now()
		// 增加请求的头消息
		for k, v := range request.Header {
			for _, vv := range v {
				req.Header.Add(k, vv)
			}
		}
		for _, c := range request.Cookies() {
			req.Header.Add("Set-Cookie", c.Raw)
		}
		resp, error := InternalClient.Do(req)
		endTime := time.Now()
		log.Infof("serviceName: %s, url:%s, cost:%vms", serviceName, targetUrl, endTime.Sub(startTime).Milliseconds())
		if error != nil {
			log.Error(error)
			return
		}
		// read body
		if resp != nil && resp.Body != nil {
			defer resp.Body.Close()
		}
		content, _ := ioutil.ReadAll(resp.Body)
		// 增加响应的头消息
		for k, v := range resp.Header {
			for _, vv := range v {
				writer.Header().Add(k, vv)
			}
		}
		for _, c := range resp.Cookies() {
			writer.Header().Add("Set-Cookie", c.Raw)
		}
		writer.Header().Add("Content-Type", resp.Header.Get("Content-Type"))
		writer.Header().Add("Content-Disposition", resp.Header.Get("Content-Disposition"))
		writer.WriteHeader(resp.StatusCode)
		num, error := writer.Write(content)
		if error != nil {
			log.Error(num, error)
		}
	})
	log.Infof("http listen: %s", addr)
	error := http.ListenAndServe(addr, nil)
	if error != nil {
		log.Error(error)
	}
}

func handleInternalPath(request *http.Request, path string, writer http.ResponseWriter) bool {
	if strings.HasPrefix(path, "/ping") {
		writer.Header().Add("content-type", "application/json")
		writer.WriteHeader(200)
		writer.Write([]byte("pong"))
		return true
	}
	if strings.HasPrefix(path, "/log") {
		writer.Header().Add("content-type", "application/json")
		writer.WriteHeader(200)
		// 获取日志
		body, _ := ioutil.ReadFile("thomas-mesh.log")
		writer.Write(body)
		return true
	}
	if strings.HasPrefix(path, "/favicon.ico") {
		writer.WriteHeader(404)
		writer.Write([]byte("Not Found"))
		return true
	}
	if strings.HasPrefix(path, "/info") {
		writer.WriteHeader(200)
		writer.Write([]byte("name:" + pkg.Name + "\n" +
			"version:" + pkg.Version))
		return true
	}
	if strings.HasPrefix(path, "/agentInfo") {
		writer.WriteHeader(200)
		body, _ := json.Marshal(discovery.AgentInfo)
		writer.Write(body)
		return true
	}

	if strings.HasPrefix(path, "/cache") {
		writer.WriteHeader(200)
		body, _ := json.Marshal(discovery.CacheAdrress)
		writer.Write(body)
		return true
	}

	if strings.HasPrefix(path, "/pts") {
		requestbytes, _ := ioutil.ReadAll(request.Body)
		var ptsReq = PtsReq{}
		json.Unmarshal(requestbytes, &ptsReq)
		resultChan := make(chan RequestDetail, ptsReq.Users*ptsReq.RequestNum)
		for userNum := 0; userNum < int(ptsReq.Users); userNum++ {
			go func() {
				defer func() {
					if e := recover(); e != nil {
						log.Errorf("recover:%v", e)
					}
				}()
				for requestNum := 0; requestNum < int(ptsReq.RequestNum); requestNum++ {
					request, _ := http.NewRequest(ptsReq.Method, ptsReq.Url, strings.NewReader(ptsReq.RequestBody))
					beforeTime := time.Now()
					resp, error := InternalClient.Do(request)
					var info string
					if resp == nil || error != nil {
						info = error.Error()
					} else {
						info = resp.Status
					}
					afterTime := time.Now()
					var costTime = afterTime.Sub(beforeTime).Milliseconds()
					resultChan <- RequestDetail{
						Url:                ptsReq.Url,
						StartTime:          beforeTime.UnixNano() / 1e6,
						EndTime:            afterTime.UnixNano() / 1e6,
						CostTime:           costTime,
						ResponseStatusCode: info,
					}
				}
			}()
		}
		var ptsResp = PtsResp{
			TotalMillisTime: 0,
			StartTime:       0,
			EndTime:         0,
			Details:         []RequestDetail{},
		}
		var startTime int64
		var endTime int64 = 0
		var totalCostTime int64 = 0
		var flag bool = false
		var count = int(ptsReq.Users * ptsReq.RequestNum)
		for num := 0; num < count; num++ {
			select {
			case detail := <-resultChan:
				if !flag {
					startTime = time.Now().UnixNano() / 1e6
					flag = true
				}
				ptsResp.Details = append(ptsResp.Details, detail)
				startTime = int64(math.Min(float64(detail.StartTime), float64(startTime)))
				endTime = int64(math.Max(float64(detail.EndTime), float64(endTime)))
				totalCostTime += detail.CostTime
			}
		}
		close(resultChan)
		ptsResp.EndTime = endTime
		ptsResp.StartTime = startTime
		ptsResp.TotalMillisTime = endTime - startTime
		ptsResp.AvgTime = totalCostTime / int64(count)
		body, _ := json.Marshal(ptsResp)
		writer.Header().Set("content-type", "application/json")
		writer.WriteHeader(200)
		writer.Write(body)
		return true
	}
	return false
}

func resolveServiceName(request *http.Request) string {
	path := request.URL.Path
	serviceName := strings.Split(path, "/")[1]
	log.Println("serviceName: ", serviceName)
	return serviceName
}
